﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using DomainModel;
using Repository.Abstract;
using Services.Abstract;

namespace Services.Concrete
{
    public class TimetableUnitService : Service<TimetableUnit>, ITimetableUnitService
    {
        private readonly ITimetableUnitRepository _timetableUnitRepository;

        private readonly IWorkTypeService _workTypeService;

        public TimetableUnitService(ITimetableUnitRepository timetableCellRepository, IPrincipal currentUser, IWorkTypeService workTypeService, IPermissionService permissionService)
            : base(timetableCellRepository, currentUser, permissionService)
        {
            _timetableUnitRepository = timetableCellRepository;
            _workTypeService = workTypeService;
        }

        public List<User> GetTeachersBySubjectId(long subjectId)
        {
            if (subjectId <= 0)
            {
                throw new ArgumentNullException("subjectId");
            }

            return _timetableUnitRepository.GetTeachersBySubjectId(subjectId).ToList();
        }

        public TimetableUnit GetTimetableUnit(StudySemester semester, int week, int day, int pairNo, Group group)
        {
            if (semester == null)
            {
                throw new ArgumentNullException("semester");
            }

            if (week < 1 || week > 2)
            {
                throw new Exception("Неверно указана неделя.");
            }

            if (day < 1 || day > 7)
            {
                throw new Exception("Неверно указан день.");
            }

            if (pairNo < 1 || pairNo > 7)
            {
                throw new Exception("Неверно указан номер пары.");
            }

            if (group == null)
            {
                throw new ArgumentNullException("group");
            }

            return _timetableUnitRepository.GetByWeekDayPairNoGroup(semester, week, day, pairNo, group);
        }

        public TimetableUnit GetTimetableUnit(StudySemester semester, int week, int day, int pairNo, Group group, Subject subject)
        {
            if (semester == null)
                throw new ArgumentNullException("semester");

            if (group == null)
                throw new ArgumentNullException("group");

            if (subject == null)
                throw new ArgumentNullException("subject");

            if (week < 1 || week > 2)
                throw new Exception("Неверно указана неделя.");

            if (day < 1 || day > 7)
                throw new Exception("Неверно указан день.");

            if (pairNo < 1 || pairNo > 7)
                throw new Exception("Неверно указан номер пары.");

            return _timetableUnitRepository.GetByWeekDayPairNoGroupSubject(semester, week, day, pairNo, group, subject);
        }

        public TimetableUnit GetTimetableUnit(StudySemester semester, int week, int day, int pairNo, Group group, User teacher)
        {
            if (semester == null)
                throw new ArgumentNullException("semester");

            if (group == null)
                throw new ArgumentNullException("group");

            if (teacher == null)
                throw new ArgumentNullException("teacher");

            if (week < 1 || week > 2)
                throw new Exception("Неверно указана неделя.");

            if (day < 1 || day > 7)
                throw new Exception("Неверно указан день.");

            if (pairNo < 1 || pairNo > 7)
                throw new Exception("Неверно указан номер пары.");

            return _timetableUnitRepository.GetByWeekDayPairNoGroupTeacher(semester, week, day, pairNo, group, teacher);
        }

        public TimetableUnit GetTimetableUnit(StudySemester semester, int week, int day, int pairNo, Group group, User teacher, Subject subject)
        {
            if (semester == null)
                throw new ArgumentNullException("semester");

            if (group == null)
                throw new ArgumentNullException("group");

            if (teacher == null)
                throw new ArgumentNullException("teacher");

            if (subject == null)
                throw new ArgumentNullException("subject");

            if (week < 1 || week > 2)
                throw new Exception("Неверно указана неделя.");

            if (day < 1 || day > 7)
                throw new Exception("Неверно указан день.");

            if (pairNo < 1 || pairNo > 7)
                throw new Exception("Неверно указан номер пары.");

            return _timetableUnitRepository.GetByWeekDayPairNoGroupTeacherSubject(semester, week, day, pairNo, group, teacher, subject);
        }

        public List<TimetableUnit> GetTimetableUnit(StudySemester semester, int week, int day, int pairNo, User teacher)
        {
            if (semester == null)
            {
                throw new ArgumentNullException("semester");
            }

            if (week < 1 || week > 2)
            {
                throw new Exception("Неверно указана неделя.");
            }

            if (day < 1 || day > 7)
            {
                throw new Exception("Неверно указан день.");
            }

            if (pairNo < 1 || pairNo > 7)
            {
                throw new Exception("Неверно указан номер пары.");
            }

            if (teacher == null)
            {
                throw new ArgumentNullException("teacher");
            }

            return _timetableUnitRepository.GetByWeekDayPairNoTeacher(semester, week, day, pairNo, teacher).ToList();
        }

        public List<TimetableUnit> GetTimetableUnits(StudySemester semester, int week, int day, int pairNo)
        {
            if (semester == null)
            {
                throw new ArgumentNullException("semester");
            }

            if (week < 1 || week > 2)
            {
                throw new Exception("Неверно указана неделя.");
            }

            if (day < 1 || day > 7)
            {
                throw new Exception("Неверно указан день.");
            }

            if (pairNo < 1 || pairNo > 7)
            {
                throw new Exception("Неверно указан номер пары.");
            }

            return _timetableUnitRepository.GetByWeekDayPairNo(semester, week, day, pairNo).ToList();
        }

        public List<TimetableUnit> GetTimetableUnit(StudySemester semester, int week, int day, int pairNo, string room)
        {
            if (semester == null)
            {
                throw new ArgumentNullException("semester");
            }

            if (week < 1 || week > 2)
            {
                throw new Exception("Неверно указана неделя.");
            }

            if (day < 1 || day > 7)
            {
                throw new Exception("Неверно указан день.");
            }

            if (pairNo < 1 || pairNo > 7)
            {
                throw new Exception("Неверно указан номер пары.");
            }

            if (string.IsNullOrEmpty(room))
            {
                throw new ArgumentNullException("room");
            }

            return _timetableUnitRepository.GetByWeekDayPairNoRoom(semester, week, day, pairNo, room).ToList();
        }

        public string GetSubjectsByTeacher(User teacher)
        {
            var subjects = _timetableUnitRepository.GetSubjectsByTeacher(teacher);

            string result = "";

            foreach (var subject in subjects)
            {
                result += subject.Name + "; ";
            }

            return result.Substring(0, result.Length - 2);
        }

        public new void Delete(TimetableUnit timetableUnit)
        {
            if (!PermissionService.CanCUDTimetableUnit(timetableUnit))
                throw new Exception("Недостаточно прав чтобы изменять расписание.");

            base.Delete(timetableUnit);
        }

        public new void SaveOrUpdate(TimetableUnit timetableUnit)
        {
            if (!PermissionService.CanCUDTimetableUnit(timetableUnit))
                throw new Exception("Недостаточно прав чтобы изменять расписание.");

            #region Null или неправильные значения

            if (timetableUnit == null)
            {
                throw new ArgumentNullException("timetableUnit");
            }

            if (timetableUnit.Week < 1 || timetableUnit.Week > 2)
            {
                throw new Exception("Неверно указана неделя.");
            }

            if (timetableUnit.Day < 1 || timetableUnit.Day > 7)
            {
                throw new Exception("Неверно указан день.");
            }

            if (timetableUnit.PairNo < 1 || timetableUnit.PairNo > 7)
            {
                throw new Exception("Неверно указан номер пары.");
            }

            if (timetableUnit.Group == null)
            {
                throw new Exception("Не указана группа.");
            }

            if (timetableUnit.Subject == null)
            {
                throw new Exception("Не указан предмет.");
            }

            if (timetableUnit.StudySemester == null)
            {
                throw new Exception("Не указан учебный семестр.");
            }

            if (timetableUnit.Teacher == null)
            {
                throw new Exception("Не указан преподаватель.");
            }

            if (timetableUnit.Teacher.Role != Role.Teacher)
            {
                throw new Exception("Преподаватель - не преподаватель.");
            }

            if (string.IsNullOrEmpty(timetableUnit.Room))
            {
                throw new Exception("Не указана аудитория.");
            }

            #endregion

            // есть ли у этой группы в это время уже какая-то пара?
            var t = GetTimetableUnit(timetableUnit.StudySemester, timetableUnit.Week, timetableUnit.Day, timetableUnit.PairNo,
                                     timetableUnit.Group);
            if (t != null && t.Id != timetableUnit.Id)
            {
                throw new Exception("У группы " + t.Group.Name + " в это время уже есть пара (" + t.Subject.Name + ").");
            }

            // ведет ли преподаватель в это время уже какую-то пару?
            var tlist = GetTimetableUnit(timetableUnit.StudySemester, timetableUnit.Week, timetableUnit.Day,
                                         timetableUnit.PairNo, timetableUnit.Teacher);
            t = tlist.Count == 0 ? null : tlist.First();
            if (t != null && t.Id != timetableUnit.Id)
            {
                // если преподаватель все же ведет в время какую-то пару - то какую и в какой группе?

                // если преподаватель ведет другой предмет, то так нельзя
                if (timetableUnit.Subject.Id != t.Subject.Id)
                {
                    throw new Exception("Преподаватель " + t.Teacher.UserProfile.Fio + " в это время ведет другой предмет (" + t.Subject.Name + ") у группы " + t.Group.Name + ".");
                }

                // если преподаватель ведет такой же предмет, то возможно это поток или т.п.
                // но тогда аудитория должна быть одна и таже
                if (timetableUnit.Room != t.Room)
                {
                    throw new Exception("Преподаватель " + t.Teacher.UserProfile.Fio + " в это время ведет этот предмет (" + t.Subject.Name + ") у группы " + t.Group.Name + " в другой аудитории (" + t.Room + ").");
                }

                // если и аудитории совпадают, то нужно чтобы и тип занятия был один и тот же
                if (timetableUnit.WorkType != t.WorkType)
                {
                    throw new Exception("Преподаватель " + t.Teacher.UserProfile.Fio + " в это время ведет " + _workTypeService.GetWorkTypeName(t.WorkType) + " по этому предмету (" + timetableUnit.Subject.Name + ") у группы " + t.Group.Name + " в этой аудитории (" + t.Room + ").");
                }

                // если все условия выше выполняются, то значит преподаватель ведет предмет одновременно у нескольких групп в одной аудитории, что возможно
            }

            // занята ли аудитория в это время?
            tlist = GetTimetableUnit(timetableUnit.StudySemester, timetableUnit.Week, timetableUnit.Day, timetableUnit.PairNo, timetableUnit.Room);
            t = tlist.Count == 0 ? null : tlist.First();
            if (t != null && t.Id != timetableUnit.Id)
            {
                // есди аудитория все же занята - то какой там преподаватель, предмет и т.п. 
                // возможно, там должна быть пара у потока

                // если аудитория занята другим преподавателем, то так нельзя
                if (timetableUnit.Teacher.Id != t.Teacher.Id)
                {
                    throw new Exception("В аудитории " + t.Room + " в это время у группы " + t.Group.Name + " ведет пару (" + t.Subject.Name + ") другой преподаватель (" + t.Teacher.UserProfile.Fio + ").");
                }

                // если в аудитории этот же преподаватель - то какой он там предмет ведет?
                if (timetableUnit.Subject.Id != t.Subject.Id)
                {
                    throw new Exception("В аудитории " + t.Room + " в это время преподаватель - " + t.Teacher.UserProfile.Fio + " ведет другой предмет (" + t.Subject.Name + ") у группы " + t.Group.Name + ".");
                }

                // если и предмет такой же - то тот ли тип занятия?
                if (timetableUnit.WorkType != t.WorkType)
                {
                    throw new Exception("В аудитории " + t.Room + " в это время преподаватель - " + t.Teacher.UserProfile.Fio + " ведет " + _workTypeService.GetWorkTypeName(t.WorkType) + " по этому предмету (" + t.Subject.Name + ") в группе " + t.Group.Name + ".");
                }

                // если все условия выше выполняются, то в этой аудитории должна быть пара по одному предмету у нескольких групп сразу
            }

            base.SaveOrUpdate(timetableUnit);
        }
    }
}